<?php

class meccano
{
   var $DB;
   var $system    = array();
   var $kernel    = false;
   var $current_module = null;
   var $events    = array();
   var $types     = array('string','integer','float','email','url','ip','list','bool','file');
   var $hide_root = false;
   var $pattern   = '::';

   var $serial    = 20080130;
   var $version   = '0.9';

   var $_depth    = false;
   var $_disabled = array();
   var $_order    = ' ASC';
   var $_leaves   = false;
   var $_lang     = '';

   function meccano(&$DB)
   {
      $this->DB = $DB;
      $this->_depth = 0;
      $this->_order = " ASC";

      /**
       * Config builder
       */
      $this->DB->sql = "SELECT * FROM meccano_config";
      $this->DB->DB_Fetch();

      for ($i=0; $i<$this->DB->rows; $i++)
      {
         if($this->DB->fetched[$i]['type'] == 'list')
         {
            $this->system[$this->DB->fetched[$i]['var_name']] = explode(',', strtolower(trim($this->DB->fetched[$i]['var_value'])));
         } elseif(preg_match("/$this->pattern/", $this->DB->fetched[$i]['var_name']) == true)
         {
            $vars = explode($this->pattern, $this->DB->fetched[$i]['var_name']);
            $this->system[$vars[1]]['depend'] = $vars[0];
            $this->system[$vars[1]]['value'] = $this->DB->fetched[$i]['var_value'];
         } else
         {
            $this->system[$this->DB->fetched[$i]['var_name']] = $this->DB->fetched[$i]['var_value'];
         }
      }

      /**
       * Module loader
       */
      $this->DB->sql = 'SELECT * FROM meccano_kernel';
      $this->DB->DB_Fetch();

      for($i=0; $i<$this->DB->rows; $i++)
      {
         $arr['id']         = $this->DB->fetched[$i]['id'];
         $arr['module']     = $this->DB->fetched[$i]['module'];
         $arr['title']      = $this->DB->fetched[$i]['title'];
         $arr['serial']     = $this->DB->fetched[$i]['serial'];
         $arr['version']    = $this->DB->fetched[$i]['version'];
         $arr['adminfile']  = $this->DB->fetched[$i]['adminfile'];
         $arr['classname']  = $this->DB->fetched[$i]['classname'];
         $arr['classfile']  = $this->DB->fetched[$i]['classfile'];
         $arr['table']      = $this->DB->fetched[$i]['table'];
         $arr['treedepend'] = $this->DB->fetched[$i]['treedepend'];
         $arr['userdepend'] = $this->DB->fetched[$i]['userdepend'];
         $arr['gallery']    = $this->DB->fetched[$i]['gallery'];
         $arr['calendar']   = $this->DB->fetched[$i]['calendar'];
         $arr['sys']        = $this->DB->fetched[$i]['sys'];
         settype($arr['id'], 'int');
         settype($arr['serial'], 'int');

         $this->kernel[$this->DB->fetched[$i]['id']] = $arr;
      }
      
      /**
       * Events loader
       */
      $this->DB->sql = 'SELECT * FROM meccano_events';
      $this->DB->DB_Fetch();
      for($i=0,$t=$this->DB->rows; $i<$t; $i++)
      {
         $this->events[$this->DB->fetched[$i]['id']] = $this->DB->fetched[$i];
      }

      $this->SetParams();
   }

   /**
     * Get Editable System Variables
     *
     * @param integer $module - get variables depends to $module
     * @param string $type - get variables in $type = $this->types;
     * @return unknown
     */
   function GetSysVars($module = false, $type = false)
   {
      isDigit($module);
      if($module !== false)
      {
         $module = " AND module = " . $module;
      } else
      {
         $module = '';
      }

      if(in_array($type, $this->types))
      {
         $this->DB->sql = "SELECT * FROM meccano_config WHERE system = 0 AND type = '$type'" . $module;
      } else
      {
         $this->DB->sql = "SELECT * FROM meccano_config WHERE system = 0" . $module;
      }

      $this->DB->DB_Fetch();

      if($this->DB->rows > 0)
      {
         return $this->DB->fetched;
      }
      return false;
   }

   /**
   * Update System variables
   *
   * @param array $vars - array of system vars
   * @return boll true|false
   */
   function UpdateSysVars($Vname, $newData)
   {
      isAlpha($Vname);
      if($Vname != false)
      {
         $this->DB->sql = 'SELECT * FROM meccano_config WHERE var_name = "' . $Vname . '"';
         $this->DB->DB_Fetch();
         if($this->DB->rows != 0)
         {
            $newData = $this->_CheckType($newData, $this->DB->fetched[0]['type']);
            if($newData !== false)
            {
               $this->DB->DB_QueryBuilder('UPDATE meccano_config SET var_value = "%s" WHERE var_name = "%s" AND system = "0"', array($newData, $Vname));
               return $this->DB->DB_Query();
            }
         }
      }

      return false;
   }

   /**
    * Save Info about Company
    *
    * @param array $vars
    * @return bool
    */
   function SetCompanyInfo($vars)
   {
      $company = serialize($vars);
      $this->DB->DB_QueryBuilder("UPDATE meccano_config SET var_value = '%s' WHERE var_name = 'company'",
      array($company));

      if($this->DB->DB_Query())
      {
         $this->system['company'] = $company;
         return true;
      }

      return false;
   }

   /**
    * Get Info about Company
    *
    * @return array|false
    */
   function GetCompanyInfo()
   {
      $company = false;

      $vars = isset($this->system['company']) ? $this->system['company'] : false;

      if($vars != false)
      {
         $company = @unserialize($vars);
      }

      return $company;
   }

   /**
    * Get ID of module;
    * @param string $module
    * @return integer|false
    */
   function GetModuleId($module)
   {
      if($this->kernel != false && count($this->kernel) > 0)
      {
         foreach ($this->kernel AS $value)
         {
            if($value['module'] == $module) return $value['id'];
         }
      }

      return false;
   }


   /**
    * Get id for New Entry in DB with two fields AS PRIMARY KEY
    *
    * @param string $table
    * @return integer
    */
   function GetTableKey($table)
   {
      $key = 1;
      $this->DB->sql = 'SELECT id FROM ' . $table . ' ORDER BY id DESC LIMIT 0,1';
      $this->DB->DB_Query();
      if($this->DB->DB_GetRows() > 0)
      {
         $key = 1 + $this->DB->DB_GetResult(0, 'id');
      }
      
      return $key;
   }

   /**
     * Get Vars for Given Node
     *
     * @param integer $id
     * @return array|false
     */
   function GetNode($id)
   {
      isDigit($id);
      if($id == false) return false;
      $arr = false;

      $this->DB->sql = 'SELECT t1.id AS id, t2.notes AS notes, t1.subid AS trunk, t1.lock, t1.module AS module, t1.folder, t1.allow_depth, t2.title AS title, t2.lang AS lng FROM meccano_tree AS t1 LEFT JOIN meccano_treetranslates AS t2 ON t1.id = t2.id WHERE t1.id = '.$id.' AND t2.lang = "'.$this->_lang.'";';
      $this->DB->DB_Fetch();

      if($this->DB->rows > 0)
      {
         $arr['id']          = $this->DB->fetched[0]['id'];
         $arr['title']       = $this->DB->fetched[0]['title'];
         $arr['trunk']       = $this->DB->fetched[0]['trunk'];
         $arr['allow_depth'] = $this->DB->fetched[0]['allow_depth'];
         settype($arr['trunk'], 'integer');

         /*
         for($i=0,$t=$this->DB->rows; $i<$t; $i++)
         {
         $arr['notes'][$this->DB->fetched[$i]['lng']] = $this->DB->fetched[$i]['notes'];
         }
         */

         if(!in_array('lock', $this->_disabled))
         {
            $arr['lock'] = $this->_lock($this->DB->fetched[0]['lock']);
         }
         if(!in_array('folder', $this->_disabled))
         {
            $arr['folder'] = $this->DB->fetched[0]['folder'];
            isBool($arr['folder']);
         }
         $arr['module']     = $this->DB->fetched[0]['module'];
         $arr['trans'] = null;

         $this->DB->sql = "SELECT title, notes, lang FROM meccano_treetranslates WHERE id = ".$id.";";
         $this->DB->DB_Fetch();

         for ($i=0; $i<$this->DB->rows; $i++)
         {
            $arr['trans']['title'][$this->DB->fetched[$i]['lang']] = $this->DB->fetched[$i]['title'];
            $arr['trans']['notes'][$this->DB->fetched[$i]['lang']] = $this->DB->fetched[$i]['notes'];
         }

         return $arr;
      }

      return false;
   }


   /**
     * Get Tree structure
     *
     * @param integer $root  - ID for root of tree
     * @param string $module - Build tree fo $module
     * @return array
     */
   function GetTree($root = false, $module = false)
   {
      $tree = false;
      if($module != false) isAlpha($module);
      isDigit($root);

      $sql = "SELECT t1.id, t2.title, t2.notes, t1.subid AS trunk, t1.lock, t1.module, t1.folder, t1.allow_depth FROM meccano_tree AS t1, meccano_treetranslates AS t2 WHERE t1.id = t2.id";

      if($module != false && $root != false)
      {
         $sql .= " AND t1.subid = " . $root;
         $sql .= " AND t1.module = '" . $module . "'";
      } elseif($module == false && $root != false)
      {
         $sql .= " AND t1.subid = " . $root;
      } elseif($root == false && $module != false)
      {
         $sql .= " AND t1.subid IS NULL";
         $sql .= " AND t1.module = '" . $module . "'";
      } else
      {
         $sql .= " AND t1.subid IS NULL";
      }

      $sql .= " AND t2.lang = '" . $this->_lang . "'";
      $sql .= " ORDER BY t1.module, t1.id " . $this->_order;

      $this->DB->sql = $sql;
      $tree = $this->_MakeNode($module);

      return $tree;
   }


   /**
     * Get a Path to Goven Node from Root
     *
     * @param integer $node
     * @return array
     */
   function GetPath($node)
   {
      isDigit($node);

      if($node == false) return false;

      $this->DB->sql = "SELECT t1.id, t1.subid, t1.module, t2.title, t2.lang FROM meccano_tree AS t1 LEFT JOIN meccano_treetranslates AS t2 ON t1.id = t2.id WHERE t1.id=".$node." AND t2.lang = '".$this->_lang."';";

      if($this->DB->DB_Query() !== false)
      {
         $trunk = $this->DB->DB_GetResult(0, 'subid');
         settype($trunk, 'integer');
         $arr['id'] = $this->DB->DB_GetResult(0, 'id');
         $arr['title'] = $this->DB->DB_GetResult(0, 'title');
         $arr['lang'] = $this->DB->DB_GetResult(0, 'lang');
         if(!in_array('module', $this->_disabled)) $arr['module'] = $this->DB->DB_GetResult(0, 'module');
         $path = array();
         $path[] = $arr;
         $tmp = $this->GetPath($trunk);
         if($tmp !== false)
         {
            $path = array_merge($tmp, $path);
         }
      } else
      {
         return false;
      }
      return $path;
   }


   /**
     * Add Node to the Tree
     *
     * @param integer $member - parent branch
     * @param array $values
     * @return bool true|false
     */
   function AddNode($member, $values)
   {
      $exec_status = false;
      isDigit($member);

      $this->DB->DB_QueryBuilder("INSERT INTO meccano_tree (subid, module, allow_depth) SELECT %i, '%s', allow_depth-1 FROM meccano_tree WHERE id=%i AND allow_depth > 0", array($member, $values['module'], $member));
      $this->DB->DB_Query();
      $this->DB->sql = "SELECT LAST_INSERT_ID() FROM meccano_tree;";
      $exec_status = $this->DB->DB_Query();
      $last_id = $this->DB->DB_GetResult(0,0);

      $title = &$values['title'];
      $notes = &$values['notes'];
      foreach ($this->system['lang'] as $lang)
      {
         $this->DB->DB_QueryBuilder("INSERT INTO meccano_treetranslates (id, lang, title, notes) VALUES ('%i', '%s', '%s', '%s');", array($last_id, $lang, $title[$lang], $notes[$lang]));
         $exec_status = $this->DB->DB_Query();
      }

      if($member != false)
      {
         $this->DB->sql = "UPDATE meccano_tree SET folder = '1' WHERE id = ".$member;
         $this->DB->DB_Query();
      }

      return $exec_status;
   }


   /**
     * Update Node info
     *
     * @param integer $node
     * @param array $values
     * @return bool true|false
     */
   function UpdateNode($node, $values)
   {
      $exec_status = false;

      //$this->DB->DB_QueryBuilder("UPDATE meccano_tree SET notes = '%s' WHERE id = %i;", array($values['notes'], $node));
      //$this->DB->DB_Query();

      $title = &$values['title'];
      $notes = &$values['notes'];
      foreach ($this->system['lang'] as $lang)
      {
         $this->DB->DB_QueryBuilder("UPDATE meccano_treetranslates SET title = '%s', notes = '%s' WHERE id = %i AND lang = '%s';", array($title[$lang], $notes[$lang], $node, $lang));
         $exec_status = $this->DB->DB_Query();
         $exec_sql = $this->DB->DB_Info();
         if($exec_sql['matched'] == 0)
         {
            $this->DB->DB_QueryBuilder("INSERT IGNORE INTO meccano_treetranslates (id, lang, title, notes) VALUES ('%i', '%s', '%s', '%s');", array($node, $lang, $title[$lang], $notes[$lang]));
            $exec_status = $this->DB->DB_Query();
         }
      }

      return $exec_status;
   }


   /**
     * Drop tree under given $root ID
     *
     * @param integer $root
     * @return bool true|false
     */
   function DropTree($root)
   {
      isDigit($root);
      if($root == false) return false;

      $this->DB->sql = "SELECT id FROM meccano_tree WHERE subid = ".$root;
      $this->DB->DB_Fetch();

      if($this->DB->rows > 0)
      {
         $branches = $this->DB->fetched;

         for($i=0, $t=$this->DB->rows; $i<$t; $i++)
         {
            $this->DropTree($branches[$i]['id']);
         }
      }

      $this->DB->sql = "SELECT t1.subid, COUNT(t2.subid) AS counts FROM meccano_tree AS t1, meccano_tree AS t2 WHERE t1.id = ".$root." AND t2.subid = t1.subid GROUP BY t2.subid;";
      $this->DB->DB_Query();
      if(($counts = $this->DB->DB_GetResult(0, 'counts')) === false || $counts == 1)
      {
         $this->DB->sql = "UPDATE meccano_tree SET folder = '0' WHERE id  = ".$this->DB->DB_GetResult(0, 'subid');
         $this->DB->DB_Query();
      }
      $this->DB->sql = "DELETE FROM meccano_treetranslates WHERE id = ".$root;
      $this->DB->DB_Query();
      $this->DB->sql = "DELETE FROM meccano_tree WHERE id = ".$root;
      $this->DB->DB_Query();

      return true;
   }


   /**
     * Get Flat List of a Tree under given $root ID
     *
     * @param integer $root
     * @param string $module
     * @return array
     */

   function GetFlatList($root = false, $module = false, $path = false, $level = 0)
   {
      if($this->_depth !== false && $this->_depth < $level) return false;

      isAlpha($module);
      isDigit($root);
      $flat_tree = false;
      $branch    = false;
      if($root == false && $module != false)
      {
         $root = $this->GetIdForNode($module);
         return $this->GetFlatList($root);
      }

      if($root != false)
      {
         if($this->_order == " DESC")
         {
            $order = " t1.id DESC";
         } else
         {
            $order = " BINARY t2.title ASC";
         }

         if($path == false)
         {
            $sql = "SELECT t1.id AS id";
            if(!in_array('notes', $this->_disabled))   $sql .= ", t2.notes AS notes";
            if(!in_array('trunk', $this->_disabled))   $sql .= ", t1.subid AS trunk";
            if(!in_array('lock', $this->_disabled))    $sql .= ", t1.lock";
            if(!in_array('module', $this->_disabled))  $sql .= ", t1.module AS module";
            if(!in_array('folder', $this->_disabled))  $sql .= ", t1.folder AS folder";
            $sql .= ", t2.title AS title FROM meccano_tree AS t1 LEFT JOIN meccano_treetranslates AS t2 ON t1.id = t2.id";
            $sql .= " WHERE t1.id = '".$root."' AND t2.lang = '".$this->_lang."'";
            //$sql .= " AND t1.subid IS NULL";
            $sql .= " ORDER BY ".$order;

            $this->DB->sql = $sql;
            $tree = $this->DB->DB_Fetch();

            if($tree != false)
            {
               if($this->hide_root == false || @$tree[0]['trunk'] != null)
               {
                  if($this->_leaves === false || $tree[0]['folder'] == 0)
                  {
                     $flat_tree = $tree;
                     $flat_tree[0]['path'][] = $this->DB->fetched[0]['title'];
                  }
               }
               $path[] = $this->DB->fetched[0]['title'];
            }
            $level++;
         }

         $sql = "SELECT t1.id AS id";
         if(!in_array('notes', $this->_disabled))   $sql .= ", t2.notes AS notes";
         if(!in_array('trunk', $this->_disabled))   $sql .= ", t1.subid AS trunk";
         if(!in_array('lock', $this->_disabled))    $sql .= ", t1.lock";
         if(!in_array('module', $this->_disabled))  $sql .= ", t1.module AS module";
         if(!in_array('folder', $this->_disabled))  $sql .= ", t1.folder AS folder";
         $sql .= ", t2.title AS title FROM meccano_tree AS t1 LEFT JOIN meccano_treetranslates AS t2 ON t1.id = t2.id";
         $sql .= " WHERE t1.subid = '".$root."' AND t2.lang = '".$this->_lang."'";
         $sql .= " ORDER BY ".$order;

         $this->DB->sql = $sql;
         $tree = $this->DB->DB_Fetch();

         for ($i=0, $t=$this->DB->rows; $i<$t; $i++)
         {
            $position = false;

            if($this->_leaves === false || $tree[$i]['folder'] == 0)
            {
               $flat_tree[] = $tree[$i];
               $flat_tree[count($flat_tree)-1]['path']   = $path;
               $flat_tree[count($flat_tree)-1]['path'][] = $tree[$i]['title'];
               $position = $flat_tree[count($flat_tree)-1]['path'];
            } else
            {
               if($path != false) $position = $path;
               $position[] = $tree[$i]['title'];
            }

            $branch = $this->GetFlatList($tree[$i]['id'], false, $position, $level+1);

            if(is_array($branch) && is_array($flat_tree))
            {
               $flat_tree = array_merge($flat_tree, $branch);
            } elseif (is_array($branch))
            {
               $flat_tree = $branch;
            }
         }
         return $flat_tree;
      }
   }

   /**
      * Get not Folder Nodes
      *
      * @param integer $root - id of node
      * @param string $module - name of module
      * @return array
      */
   function GetLeaves($root = false, $module = false)
   {
      isAlpha($module);
      isDigit($root);

      if($root == false && $module != false)
      {
         $root = $this->GetIdForNode($module);
         return $this->GetLeaves($root, false);
      }

      $leaves_tree = $this->GetFlatList($root, $module);
      if($leaves_tree == false) return false;

      foreach ($leaves_tree AS $value)
      {
         if($value['folder'] == 0)
         {
            $tree[] = $value;
         }
      }
      unset($leaves_tree);

      return $tree;
   }

   /**
     * Get Root ID of a module
     *
     * @param string $module
     * @return integer
     */
   function GetIdForNode($module)
   {
      $this->DB->sql = "SELECT id FROM meccano_tree WHERE module = '".$module."' AND subid IS NULL;";
      $this->DB->DB_Query();

      if($this->DB->DB_GetRows() > 0) return $this->DB->DB_GetResult(0, 'id');

      return false;
   }


   /**
      * Set disabled fields
      *
      * @param string $disable - coma separated list of disabled fields
      * @return bool
      */
   function SetDisabled($disable = '')
   {
      if($disable == '')
      {
         $this->_disabled = array();
         return true;
      }

      $disable = explode(',', $disable);
      array_map("isAlpha", $disable);
      $disable = array_values($disable);

      if(count($disable) > 0)
      {
         $this->_disabled = $disable;
         return true;
      }

      return false;
   }


   function SetParams($params = 'clear')
   {
      if($params == 'clear')
      {
         $this->_order    = " ASC";
         $this->hide_root = false;
         $this->_leaves   = false;
         $this->_lang     = $this->system['lang'][0];
         $this->_depth    = false;

         $this->SetDisabled();
         return true;
      }

      $params = explode(';', $params);

      foreach ($params as $value)
      {
         if($value == 'clear') $this->SetParams();

         $p = explode('=', $value);
         switch ($p[0])
         {
            case 'order':
               if($p[1] == '-' || $p[1] == 'desc') $this->_order = ' DESC';
               if($p[1] == '+' || $p[1] == 'asc')  $this->_order = ' ASC';
               break;

            case 'root':
               if($p[1] == 'hide') $this->hide_root = true;
               if($p[1] == 'show') $this->hide_root = false;
               break;

            case 'leaves':
               if($p[1] == 'yes') $this->_leaves = true;
               if($p[1] == 'no')  $this->_leaves = false;
               break;

            case 'language':
               if(in_array($p[1], $this->system['lang']))
               {
                  $this->_lang = $p[1];
               } else
               {
                  $this->_lang = $this->system['lang'][0];
               }
               break;

            case 'depth':
               isDigit($p[1], 'NOZERRO');
               $this->_depth = $p[1];
               break;
         }
      }

      return true;
   }


   /**
      * Get Array of Nodes ID by ROOT id
      *
      * @param integer $category_id
      * @param string $lang
      * @return array
      */
   function ListOfID($category)
   {
      isDigit($category);
      if($category == false) return false;

      $tree = $this->GetFlatList($category, false);
      if($tree == false) return false;

      for ($i=0, $t=count($tree); $i<$t; $i++)
      {
         $list[$i] = $tree[$i]['id'];
      }

      return $list;
   }


   /**
     * Make array for Node
     *
     * @param string $module
     * @return array
     */
   function _MakeNode($module)
   {
      $rows = $this->DB->DB_Fetch();
      $arr  = false;

      if($this->DB->rows > 0)
      {
         for ($i=0, $t=$this->DB->rows; $i<$t; $i++)
         {
            if($this->hide_root == true && $this->_depth == 0)
            {
               $this->_depth = $this->_depth + 1;
               $arr = $this->GetTree($rows[$i]['id'], $module);
               $this->_depth = $this->_depth - 1;
            } else
            {
               $arr[$i]['title']   = $rows[$i]['title'];
               $arr[$i]['id']      = $rows[$i]['id'];
               if(!in_array('trunk', $this->_disabled))
               {
                  $arr[$i]['trunk'] = $rows[$i]['trunk'];
                  settype($arr[$i]['trunk'], 'integer');
               }
               if(!in_array('notes', $this->_disabled)) $arr[$i]['notes'] = $rows[$i]['notes'];
               if(!in_array('lock', $this->_disabled))
               {
                  $arr[$i]['lock'] = $this->_lock($rows[$i]['lock']);
               }
               if(!in_array('module', $this->_disabled)) $arr[$i]['module']  = $rows[$i]['module'];
               if(!in_array('folder', $this->_disabled))
               {
                  $arr[$i]['folder'] = $rows[$i]['folder'];
                  isBool($arr[$i]['folder']);
               }
               $arr[$i]['allow_depth'] = $rows[$i]['allow_depth'];
               $arr[$i]['branch']  = $this->GetTree($rows[$i]['id'], $module);
               settype($arr[$i]['id'], "integer");

            }
         }

         return $arr;
      }

      return false;
   }

   /**
     * Check type of variable
     *
     * @param string $str  - string to be checked
     * @param string $type - type of the string
     * @return string
     */
   function _CheckType($str, $type)
   {
      if($str == '' || $type == '' || !in_array($type, $this->types)) return false;
      //'string','integer','float','email','url','ip','list','bool','file'

      switch ($type)
      {
         case 'string':
            //$str = prepare_str($str);
            break;

         case 'integer':
            isDigit($str);
            break;

         case 'email':
            isEmail($str);
            break;

         case 'float':
            isMoneySum($str);
            break;

         case 'url':
            isUrl($str);
            break;

         case 'ip':
            isIP($str);
            break;

         case 'list':
            break;

         case 'bool':
            if($str != 0 && $str != 1) $str = false;
            break;

         case 'file':
            if(!file_exists($_SERVER['DOCUMENT_ROOT'].$str)) $str = false;
            break;
      }

      return $str;
   }


   function _lock($int)
   {
      $arr['edit'] = false;
      $arr['del']  = false;
      $arr['add']  = false;

      if(!isDigit($int) && strlen($int) != 3) return $arr;

      $arr['edit'] = isBool(substr($int, 0, 1));
      $arr['del']  = isBool(substr($int, 1, 1));
      $arr['add']  = isBool(substr($int, 2, 1));

      return $arr;
   }
}
?>